package com.beone.admin.security.filter;

import com.alibaba.fastjson.JSONObject;
import com.base.common.util.algorithm.DigestUtils;
import com.base.common.util.httpclient.BaseHttpClientUtils;
import com.beone.admin.api.user.HunnuUserOrgsRestClient;
import com.beone.admin.api.user.UserOrgs;
import com.beone.admin.config.HunnuEduConfig;
import com.beone.admin.controller.common.ControllerUtils;
import com.beone.admin.entity.BaseUser;
import com.beone.admin.service.UrlSecurityUserDetailsService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @title  Oauth2  client  Security 身份鉴权过滤器
 * @Author 覃球球
 * @Version 1.0 on 2018/11/6.
 * @Copyright 贝旺科技
 */
public class Oauth2ClientAuthenticationFilter extends AbstractAuthenticationProcessingFilter{

    public static final String OAUTH2_USER_INFO_KEY = "oauth2_user_info_key";
    public static final String OAUTH2_TOKEN_KEY = "oauth2_token_key";
    private Logger log = LoggerFactory.getLogger(Oauth2ClientAuthenticationFilter.class);

    private HunnuEduConfig eduConfig;

    private String defaultUserPwd = "beone_123";

    private UrlSecurityUserDetailsService userDetailsService;

    
    /**
     * the default value for <tt>filterProcessesUrl</tt>.
     */
    public Oauth2ClientAuthenticationFilter() {
        super(new AntPathRequestMatcher("/oauth2", "GET"));
    }

    public HunnuEduConfig getEduConfig() {
        return eduConfig;
    }

    public void setEduConfig(HunnuEduConfig eduConfig) {
        this.eduConfig = eduConfig;
    }



    public void setUserDetailsService(UrlSecurityUserDetailsService userService) {
        this.userDetailsService = userService;
    }


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        if (SecurityContextHolder.getContext().getAuthentication() != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("SecurityContextHolder not populated with oauth token, as it already contained: '"
                        + SecurityContextHolder.getContext().getAuthentication() + "'");
            }
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            if(requiresAuthentication(request, response)){ //oauth2 授权登录后再请求oauth2资源，重定向到后台主页
                response.sendRedirect(request.getContextPath() + "/system/index");
            }else{
                filterChain.doFilter(servletRequest, servletResponse);
            }
            return;
        }
        super.doFilter(servletRequest, servletResponse, filterChain);
    }

    /**
     * Performs actual authentication.
     * <p>
     * The implementation should do one of the following:
     * <ol>
     * <li>Return a populated authentication token for the authenticated user, indicating
     * successful authentication</li>
     * <li>Return null, indicating that the authentication process is still in progress.
     * Before returning, the implementation should perform any additional work required to
     * complete the process.</li>
     * <li>Throw an <tt>AuthenticationException</tt> if the authentication process fails</li>
     * </ol>
     *
     * @param request  from which to extract parameters and perform the authentication
     * @param response the response, which may be needed if the implementation has to do a
     *                 redirect as part of a multi-stage authentication process (such as OpenID).
     * @return the authenticated user token, or null if authentication is incomplete.
     * @throws AuthenticationException if authentication fails.
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        HttpSession session = request.getSession();
        JSONObject userLoginJSON = (JSONObject) session.getAttribute(OAUTH2_USER_INFO_KEY);
        if(userLoginJSON != null){
            return getAuthentication(request, userLoginJSON);
        }

        String code = request.getParameter("code");
        if(StringUtils.isBlank(code)){ //微信未登录， 进行微信OAuth2 授权
            response.sendRedirect(redirectUrl());
            return null;
        }

        JSONObject token = BaseHttpClientUtils.getToJSON(getAccessTokenUrl(code), null);
        if(log.isDebugEnabled()){
            log.debug("access token = {}" , token);
        }
        session.setAttribute(OAUTH2_TOKEN_KEY, token);  // oauth2 access_token info
        //获取Access Token
        String access_token = token.getString("access_token");

        //获取登录用户信息
        userLoginJSON = BaseHttpClientUtils.getToJSON(getAccountInfo(access_token), null);
        if(log.isDebugEnabled()){
            log.debug("oauth2 login info = {}" , userLoginJSON);
        }
        session.setAttribute(OAUTH2_USER_INFO_KEY, userLoginJSON);

        return getAuthentication(request, userLoginJSON);
    }

    private Authentication getAuthentication(HttpServletRequest request, JSONObject userLoginJSON) {
        String account = userLoginJSON.getString("xgno");
        if(StringUtils.isBlank(account)){
            account = userLoginJSON.getString("account");
        }

        boolean result = userDetailsService.isNotExistBaseUserByOpenId(account);
        if(result){ //oauth login info not exist
            BaseUser user = new BaseUser();
            //师大统一身份认证  性别 1 男 0 女
            user.setUserSex("0".equals(userLoginJSON.getString("sex")) ? "02" : "01");
            user.setUserMobile(userLoginJSON.getString("phone"));
            user.setUserName(userLoginJSON.getString("realname"));
            user.setUserStatus("01"); //正常
            user.setUserPwd(DigestUtils.md5(defaultUserPwd));
            user.setUserAccount(account);
            user.setRoleIds(eduConfig.getDefaultRoleId()); //默认角色
            userDetailsService.saveUser(user); //保存用户信息
        }

        // 获取用户部门信息并保存 关联关系
        /*String xgno = userLoginJSON.getString("xgno");
        if(StringUtils.isNotEmpty(xgno)) {
            HunnuUserOrgsRestClient userOgsRestClient = new HunnuUserOrgsRestClient();
            userOgsRestClient.setEduConfig(eduConfig);
            UserOrgs userOrgs = new UserOrgs();
            userOrgs.setXgno(xgno);
            UserOrgs usrinfo = null;
            try {
                usrinfo = userOgsRestClient.getUserInfo(userOrgs);
                if(usrinfo == null) {
                	throw new Exception("返回用户null ");
                }else {
                	JSONArray arr = usrinfo.getOrgInfoList();
                	List<GroupUser> gulst = new ArrayList<>();
                	for(int i=0; i < arr.size(); i++) {
                		JSONObject json = arr.getJSONObject(i);
                		String groupcode = json.getString("code");
                		GroupUser gu = new GroupUser();
                		gu.setXgno(usrinfo.getXgno());
                		gu.setGroupcode(groupcode);
                		gulst.add(gu);
                	}
                	//保存用户部门关系 第二个参数是否删除 工号已经关联的部门关系
                	groupUserService.saveGroupUser(gulst,true);
                }
            } catch (Exception e) {
                log.error("获取部门信息失败 = {} " , userLoginJSON);
            }
        }*/

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                account, defaultUserPwd);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * Provided so that subclasses may configure what is put into the authentication
     * request's details property.
     *
     * @param request that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details
     * set
     */
    protected void setDetails(HttpServletRequest request,
                              UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    private String redirectUrl() {
        StringBuilder url = new StringBuilder();
        url.append(eduConfig.getOauth2ServerUrl());
        url.append("/oauth2/authorize?client_id=");
        url.append(eduConfig.getOauth2ClientId());
        url.append("&response_type=code&redirect_uri=");
        url.append(eduConfig.getOauth2RedirectUrl());
        return url.toString();
    }


    /**
     * 获取Access Token URL
     * @param code
     * @return
     */
    private String  getAccessTokenUrl(String code){
        StringBuilder url = new StringBuilder();
        url.append(eduConfig.getOauth2ServerUrl());
        url.append("/oauth2/access_token");
        url.append("?client_id=");
        url.append(eduConfig.getOauth2ClientId());
        url.append("&client_secret=");
        url.append(eduConfig.getOauth2ClientSecret());
        url.append("&code=");
        url.append(code);
        url.append("&grant_type=authorization_code&redirect_uri=");
        url.append(eduConfig.getOauth2RedirectUrl());
        return url.toString();
    }

    /**
     * 根据access_token 获取登录用户信息
     * @param accessToken
     * @return
     */
    private String getAccountInfo(String accessToken){
        StringBuilder url = new StringBuilder();
        url.append(eduConfig.getOauth2ServerUrl());
        url.append("/oauth2/user_info");
        url.append("?access_token=");
        url.append(accessToken);
        return url.toString();
    }
}